//
// Created by YANHAI on 2019/11/15.
//

#pragma once

#include <string>
#include <cstring>
#include <libnet.h>
#include <event2/http.h>
#include <event2/buffer.h>
#include <event2/keyvalq_struct.h>
#include <boost/lexical_cast.hpp>
#include <boost/timer/timer.hpp>
#include <spdlog/spdlog.h>

namespace logger = spdlog;

#define HTTP_UNAUTHORIZED 401
#define HTTP_FORBIDDEN 403

class Response {
public:
    explicit Response(evhttp_request *request);

    ~Response();

    int set_header(const char *name, const char *value, bool force = true);

    int add_header(const char *name, const char *value);

    void write_bad_request(const std::exception &e); //400

    void write_unauthorized();   // 401

    void write_forbidden();  // 403

    void write_not_found();  // 404

    void write_internal_server_error(std::exception &e);

    void write(const char *value, size_t len, int code = HTTP_OK);

    void write(const char *value, int code = HTTP_OK);

    void write(const std::string &value, int code = HTTP_OK);

private:
    void set_default_headers();

    void set_default_body();

    void print_info();

    std::string get_client_host();

private:
    evhttp_request *m_request;
    std::string m_uri;
    evhttp_cmd_type m_method;
    std::string m_method_str;
    int m_status_code;
    std::string m_client_host;
    boost::timer::cpu_timer m_times;
    evbuffer *m_buffer;
};

// detail
#define CASE_CODE(E)  case E: return #E

static const std::string _methodToString(evhttp_cmd_type method_code)
{
    std::string method;
    switch (method_code) {
        CASE_CODE(EVHTTP_REQ_GET);
        CASE_CODE(EVHTTP_REQ_POST);
        CASE_CODE(EVHTTP_REQ_HEAD);
        CASE_CODE(EVHTTP_REQ_PUT);
        CASE_CODE(EVHTTP_REQ_DELETE);
        CASE_CODE(EVHTTP_REQ_OPTIONS);
        CASE_CODE(EVHTTP_REQ_TRACE);
        CASE_CODE(EVHTTP_REQ_CONNECT);
        CASE_CODE(EVHTTP_REQ_PATCH);
    }
    return "EVHTTP_REQ_UNKNOWN";
}

static const std::string methodToString(evhttp_cmd_type method_code)
{
    auto method = _methodToString(method_code);
    return method.substr(11, method.length());
}

inline Response::Response(evhttp_request *request) : m_request(request)
{
    m_status_code = HTTP_OK;
    m_uri = evhttp_request_get_uri(m_request);
    m_method = evhttp_request_get_command(m_request);
    m_method_str = methodToString(m_method);
    m_client_host = get_client_host();
    m_buffer = evhttp_request_get_output_buffer(m_request);
}

inline Response::~Response()
{
    try {
        set_default_headers();
        set_default_body();
        evhttp_send_reply(m_request, m_status_code, "", m_buffer);
        print_info();
    } catch (...) {}    // eat any exceptions
}

/**
 *
 * @param name
 * @param value
 * @param force
 * @return 0 on success, -1  otherwise.
 */
inline int Response::set_header(const char *name, const char *value, bool force)
{
    if (!name || !value)
        return -1;

    evkeyvalq *out_headers = evhttp_request_get_output_headers(m_request);
    if (evhttp_find_header(out_headers, name)) {
        if (!force)
            return 0;
        evhttp_remove_header(out_headers, name);
    }

    return evhttp_add_header(out_headers, name, value);
}

/**
 *
 * @param name
 * @param value
 * @return 0 on success, -1  otherwise.
 */
inline int Response::add_header(const char *name, const char *value)
{
    if (!name || !value)
        return -1;

    evkeyvalq *out_headers = evhttp_request_get_output_headers(m_request);
    return evhttp_add_header(out_headers, name, value);
}

inline void Response::write_bad_request(const std::exception &e)
{
    std::string msg = "<html><title>400: BadRequest</title><body>400: BadRequest: ";
    msg += e.what();
    msg += "</body></html>";
    write(msg, HTTP_BADREQUEST);
}

inline void Response::write_unauthorized()
{
    write("<html><title>401: Unauthorized</title><body>401: Unauthorized</body></html>", HTTP_UNAUTHORIZED);
}

inline void Response::write_forbidden()
{
    write("<html><title>403: Forbidden</title><body>403: Forbidden</body></html>", HTTP_FORBIDDEN);
}

inline void Response::write_not_found()
{
    write("<html><title>404: Not Found</title><body>404: Not Found</body></html>", HTTP_NOTFOUND);
}

inline void Response::write_internal_server_error(std::exception &e)
{
    std::string msg = "<html><title>500: Internal</title><body>500: Internal: ";
    msg += e.what();
    msg += "</body></html>";
    write(msg, HTTP_INTERNAL);
}

inline void Response::write(const char *value, size_t len, int code)
{
    m_status_code = code;
    evbuffer_add(m_buffer, value, len);
}

inline void Response::write(const char *value, int code)
{
    write(value, strlen(value), code);
}

inline void Response::write(const std::string &value, int code)
{
    write(value.c_str(), value.length(), code);
}

inline void Response::set_default_headers()
{
    set_header("Content-Type", "text/html; charset=UTF-8", false);
    set_header("Server", EVENT__PACKAGE EVENT__VERSION, false);
}

inline void Response::set_default_body()
{
    if (evbuffer_get_length(m_buffer) == 0) {
        const char *value = "<html><title>200: Ok</title><body>200: Ok</body></html>";
        write(value);
    }
}

inline std::string Response::get_client_host()
{
    char client_host[48] = {0};
    auto _sockaddr = evhttp_connection_get_addr(evhttp_request_get_connection(m_request));
    inet_ntop(AF_INET, &reinterpret_cast<const sockaddr_in *>(_sockaddr)->sin_addr, client_host, sizeof(client_host));
    return std::string(client_host);
}

inline void Response::print_info()
{
    m_times.stop();
    logger::level::level_enum lvl;
    auto millisecond = boost::lexical_cast<float>(m_times.format(6, "%w")) * 1000;

    if (m_status_code >= 200 && m_status_code < 300)
        lvl = logger::level::info;
    else if (m_status_code >= 500)
        lvl = logger::level::err;
    else
        lvl = logger::level::warn;

    // 200 POST /url (127.0.0.1) 32.040ms
    logger::log(lvl, "{} {} {} ({}) {:.3f}ms",
                m_status_code,
                m_method_str, m_uri,
                m_client_host, millisecond);
}
